/*
 * Copyright (C) 2012 CNL - ECE Dept - National University of Singapore
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package sg.nus.ece.cnl.veracity.hadoop;

import com.xensource.xenapi.VM;
import com.xensource.xenapi.VMGuestMetrics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import sg.nus.ece.cnl.veracity.access.SshUserCredentials;
import sg.nus.ece.cnl.veracity.access.TargetServer;
import sg.nus.ece.cnl.veracity.common.SessionBase;
import sg.nus.ece.cnl.veracity.common.VMCreator;

/**
 * Deploy a Hadoop cluster given a number of VM instances
 *
 * @author tram
 */
public class ClusterDeployer {

    private static final Logger log = Logger.getLogger(ClusterDeployer.class);
    // hadoop deployer for this session
    private static ClusterDeployer hadoopDeployerInstance;

    private ClusterDeployer() {
    }

    public static ClusterDeployer getInstance() {

        if (hadoopDeployerInstance == null) {
            hadoopDeployerInstance = new ClusterDeployer();
        }

        return hadoopDeployerInstance;
    }

    /**
     * Deploy all VMs required by this Hadoop cluster
     *
     * @param target target server
     * @param userCredentials user credentials used for this connection
     * @param hadoopVMTemplate hadoop vm template (a server can support several)
     * @param nVMs number of VM instances required by the Hadoop cluster
     * @return a map of VM, each pair <key, value> represents vm name and its IP
     * @throws sg.nus.ece.cnl.veracity.exception.VeracityException
     */
    public Map<String, String> deployHadoopCluster(TargetServer target, SshUserCredentials userCredentials, String hadoopVMTemplate, int nVMs)
            throws sg.nus.ece.cnl.veracity.exception.VeracityException {

        // create a new session
        SessionBase mySession = new SessionBase(target, userCredentials);
        // vm list containing uuid of all vms in the Hadoop cluster
        List<String> listUUID = new ArrayList<String>();
        try {
            // connect to the server
            mySession.connect();
            
            // verify if the template exists
            if (mySession.getVMTemplate(hadoopVMTemplate) == null) {
                log.error("The VM template does not exist on the server...");
                return null;
            }
            
            // consider the moment when cluster is deployed as the session ID
            long sessionID = System.nanoTime();
            
            // TODO: verify if it is possible to deploy VMs in parallel
            for (int i = 0; i < nVMs; i++) {
                // get a name for new VM
                String newVmName;
                if (i == 0) {
                    newVmName = sessionID + "-Hadoop-Master";
                } else {
                    newVmName = sessionID + "-Hadoop-Slave-" + i;
                }

                // deploy new VM and get its IP
                VM newVm = VMCreator.getInstance(mySession).createVM(newVmName, hadoopVMTemplate);
                // in case of successfully deployed, put vm uuid to the list
                String uuid = newVm.getUuid(mySession.getConnection());
                if (uuid != null && !uuid.isEmpty()) {
                    listUUID.add(uuid);
                }
            }
            // disconnect from the server
            mySession.disconnect();
        } catch (com.xensource.xenapi.Types.XenAPIException ex) {
            // exception throwed by the XEN API
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        } catch (org.apache.xmlrpc.XmlRpcException ex) {
            // exception throwed by the XmlRpc
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        }
        return getIPs(target, userCredentials, listUUID);
    }

    /**
     * Get the IP address of VMs given their uuid on the cluster
     *
     * @param target target server
     * @param userCredentials user credentials used for this connection
     * @param vmList list of VM uuid
     * @return a map each entity represents a pair of VM name and its IP
     * @throws sg.nus.ece.cnl.veracity.exception.VeracityException
     */
    public Map<String, String> getIPs(TargetServer target, SshUserCredentials userCredentials, List<String> vmList)
            throws sg.nus.ece.cnl.veracity.exception.VeracityException {

        Map<String, String> vmMap = new HashMap<String, String>();
        // create a new session
        SessionBase mySession = new SessionBase(target, userCredentials);
        try {
            // connect to the server
            mySession.connect();
            for (String vmUuid : vmList) {
                VM vmInstance = VM.getByUuid(mySession.getConnection(), vmUuid);
                String vmName = vmInstance.getNameLabel(mySession.getConnection());

                int waitingtime = 0;
                // waiting maximum 5 min for the VM booting, after this time, vm is considered down. 
                while (waitingtime < 5 * 60 * 1000) {

                    VMGuestMetrics vmGuestMetrics = vmInstance.getGuestMetrics(mySession.getConnection());
                    try {
                        if (vmGuestMetrics.getLive(mySession.getConnection())) {
                            String vmIP = vmGuestMetrics.getNetworks(mySession.getConnection()).get("0/ip");
                            vmMap.put(vmName, vmIP);
                            break;
                        } else {
                            // live is false means that VM is down
                            break;
                        }
                    } catch (com.xensource.xenapi.Types.HandleInvalid ex) {
                        if (waitingtime == 0) {
                            log.info("Waiting for VM \"" + vmName + "\" booting ...");
                        }
                        // vm is not fully loaded.
                        waitingtime += 2000;
                        try {
                            Thread.sleep(2000);
                        } catch (java.lang.InterruptedException e) {
                            // do nothing
                        }
                    }
                }
            }
            // disconnect from the server
            mySession.disconnect();
        } catch (com.xensource.xenapi.Types.XenAPIException ex) {
            // exception throwed by the XEN API
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        } catch (org.apache.xmlrpc.XmlRpcException ex) {
            // exception throwed by the XmlRpc
            throw new sg.nus.ece.cnl.veracity.exception.VeracityException(ex);
        }
        return vmMap;
    }
}
